-------------------------------------------------------------------------------- Chapter 2 -------------------------------------------------------------------------------- CONTENT: --------------------------------- Part one: Introduction to Quake C --------------------------------- Chapter 2: A TOUR OF QUAKE-C A) COMPILATION AND EXECUTION A Quake-C program is a set of one or more functions and data, known as SOURCE CODE, written by the programmer with an editor program. The source code is stored in a file with an arbitrary name, by convention followed by the suffix '.qc'. To render the Quake-C program intelligible to and executable by Quake, the source code must be processed in a number of steps. First, The Quake-C source code is scanned by the Quake-C Compiler. The Compiler is itself a program which, according to the grammatical rules of the Quake-C language, parses and analyses the source code into an intermediate form known as Progs.Dat file. *Note: If you are building models (.MDL), The program Modelgen will parse the same source as QCC, so frame indexes will keep their symbolic names when compiled into the program Code. Second, Quake will use the Progs.Dat file and interpret it internally to the game and execute the appropriate code. To compile the Quake-C code, it is necessary to follow a few basic steps: To modify the quake program code, set up a new game directory parallel with id1, and containing a "progs" subdirectory. Copy all the .qc files and progs.src into that, and just run qcc from that directory. That will compile all of the files listed in progs.src and (if there aren't any errors) generate a new progs.dat file in the parent directory. As a simple test, open the client.qc file, go to the ClientObituary function at the end, and change some of the messages. The directory structure will look something like: /quake/quake.exe /quake/id1/ /quake/mygame/progs.dat /quake/mygame/progs/progs.src /quake/mygame/progs/world.qc /quake/mygame/progs/client.qc /quake/mygame/progs/... etc ... Run quake with "-game mygame", which will cause quake to look for data in the mygame directory before falling back to id1. In this example, it will find the new progs.dat from mygame, and take everything else from id1. You can type "path" at the quake console to verify the current search order of directories and pak files. THIS WILL ONLY WORK WITH A REGISTERED VERSION OF QUAKE. If you are compiling a Model (.MDL) with some Quake-C code, here are some basic steps to follow: Once you have completed make sure that all these files are in one directory: base.tri //Copy of knife.tri knife.tri //Actual 3-d model file skin.lbm //Texture for knife knife.qc //Quake-C file for knife. (what it does) Then simply run modelgen knife.qc which will then create a knife.mdl file which quake reads when running the game. If you have done everything right you should now see a knife.mdl file in the same directory. Here are some points you need to be aware of when using Quake-C: Source files are processed sequentially without dumping any state, so if a defs file is the first one processed, the definitions will be available to all other files. The language is strongly typed and there are no casts. (DEFINITION cast: An operation which converts the type of its operand to a specified type. In the cast operation (float)x, the type of X is converted to floating-point.) (DEFINITION type: The nature of a VARIABLE, decared by a programmer to specify the range of values a variable can take as well as the operations which can be performed on it.) Anything that is initialized is assumed to be constant, and will have immediates folded into it. If you change the value, your program will malfunction. All uninitialized globals will be saved to savegame files. Functions cannot have more than eight parameters. (DEFINITION function: A body of code comprising a FUNCTION HEADER immediately followed by a COMPOUND STATEMENT. A function name is an external object; a function can therefore be called from any other function, to which execution control is returned when the called function has finished.) Error recovery during compilation is minimal. It will skip to the next global definition, so you will never see more than one error at a time in a given function. All compilation aborts after ten error messages. Names can be defined multiple times until they are defined with an initialization, allowing functions to be prototyped before their definition. (DEFINITION name: The collective term used to describe all VARIABLE names, preprocessor macros and symbolic constants.) Example: void() MyFunction; // the prototype void() MyFunction = // the initialization { dprint ("we're here\n"); }; B) SIMPLE QUAKE-C PROGRAMS The Following is the minimal Quake-C function: Void() T_Damage = { }; Every Quake-C program must consist of one or more functions. The code shown above is a function. The function name is T_Damage. Functions cannot have more than eight parameters. The parentheses, (), enclose the names of PARAMETERS which may be received by the function. There are no parameter names here, but the rules for inclusion of these are given in chapter 3. (DEFINITION formal parameter: Synonymous with PARAMETER, meaning a VARIABLE defined in a FUNCTION header. The Value of the actual parameter supplied in the function call is copied to the formal parameter.) (DEFINITION variable: A memory location used to hold data of a particular TYPE, represented by an IDENTIFIER and capable of being repeatedly assigned to.) (DEFINITION identifier: The name given to a VARIABLE when it is decared.) The curly braces {} are a COMPOUND STATEMENT: in fact a null compound statement because they do not contain any statements in the example above. On execution, the function, as might be expected, does nothing. The following function is slightly more meaningful: void() T_Damage = { dprint ("Bang! Your dead!\n"); }; DPRINT is a call to a Builtin Function. It is not part of the Quake-C language itself. The dprint is nonetheless a statement, which is and must be terminated with a semicolon. the text within the parentheses is an a ARGUMENT to the called builtin function DPRINT. Executing this program causes the text: Bang! Your dead! to appear on Quake Console. the \n is Quake-C's way of specifying advance to a new line, causes the output to advance one line after the text is displayed. C) FUNCTIONS A function is a body of Quake-C code executed from another part of the program by means of a FUNCTION CALL. Functions usually contain code to perform a specific action. Instead of duplicating that code at every point in the program where the action is required, the programmer writes calls to the function, where the single definition of the code resides. Every Quake-C program is a collection of functions. The following is a simple general form for DECLARING functions: <RETURNTYPE> (arg1, arg2,...,arg8) <FUNC_NAME>; Declaring a function makes it available to all functions defined after it. It MUST be defined at some point, or the Quake-C compiler complain's. You must also put a semi-colon at the end of the Declare. An example of declaring a function is as follows: void (entity targ, entity inflictor, entity attacker, float damage) T_Damage; VOID is the Return Type. (entity targ, entity inflictor, entity attacker, float damage) is the paramater list. There are 4 arguments in the argument list with this function inside the parenthesis. T_Damage is the Function Name. Note the Semi-colon at the end of the line. (DEFINITION return type: The TYPE of the DATA OBJECT returned by a FUNCTION to the point of its call.) (DEFINITION argument list: The full list of FORMAL PARAMETERS defined in a functions header.) (DEFINITION function call: A FUNCTION name, followed by a parenthesised list of ARGUMENTS and terminated by a semicolon. The code of function named is executed and, when it is finished, the STATEMENT following the function call is executed.) The following is a simple general form for DEFINING functions: <RETURNTYPE> (arg1, arg2, ..., arg8) <FUNCT_NAME> = { <statements> }; Defining a function assigns a group of statements to it. An example of defining a function is as follows: void(entity targ, entity inflictor, entity attacker, float damage) T_Damage = { local vector dir; local entity oldself; local float save; local float take; if (!targ.takedamage) return; save = ceil(targ.armortype*damage); if (save >= targ.armorvalue) { save = targ.armorvalue; targ.armortype = 0; // lost all armor } targ.armorvalue = targ.armorvalue - save; take = ceil(damage-save); //LOTS MORE COOL DAMAGE STUFF (but snipped for space reasons) }; VOID is the Return Type. there are 4 arguments in the argument list with this function inside the parenthesis. T_Damage is the Function Name. Note the Assignment Operator(=) at the end of the first line. The code in between the outer curly braces {}; is the STATEMENTS. (meat of the function). You MUST use a semi-colon <;> after the LAST Curly brace. Calling a function (function call) is just the name of the function, followed by a comma-seperated list of arguments, enclosed in parentheses. The following is a simple general form for CALLING functions: <FUNCT_NAME> (arg1, arg2, ..., arg8); <FUNCT_NAME> This is the NAME of the function you want to call. (arg1, arg2, ..., arg8) These are the arguments you want to send to the function. Note the semi-colon at the end of the line. Here is an example of how Calling a function is used in Quake-C. (look for the function called T_Damage: /* ============================================================================== MULTI-DAMAGE Collects multiple small damages into a single damage ============================================================================== */ entity multi_ent; float multi_damage; void() ClearMultiDamage= { multi_ent = world; multi_damage = 0; }; void() ApplyMultiDamage= { if (!multi_ent) return; T_Damage (multi_ent, self, self, multi_damage); }; Every Quake-C function can and should be fully described in three parts using a declaration, call and definition. Fuller rules for use of these constructs are given in Chapter 3. Comments are included in the program between the /* and */ delimiters. The text of comments has no effect on the execution of the program. The compiler ignores comments and does not generate object code for them. Syntax errors with comments can have serious effects. If the trailing */ is left out, the compiler will keep searching for it until the end of the program and the compilation process will fail. If the characters in /* or */ are seperated by one or more spaces, for example * /, the compiler will not recognise the text between them as comments. The comment delimiters are pairs of characters. Comments must NOT be nested: /*COMMENT TEXT /* nested */ in error */ In this case, the first */ ends the comment. When the compiler tries to interpret IN ERROR */, a compilation error results. Quake-C allows the alternative double-slash comment form: // This line (ONLY) is a comment Because the comment is not explicitly terminated, this form is convenient and less error-prone for writing short comments then the conventional /* ... */ sequence. D) DATA REPRESENTATION (SIMPLE) DISCLAIMER: This section may not even be relevent to Quake-C. It is included here in the hopes that it is. It may be deleted in a future revision. If anyone can shed any light on the subject please send e-mail to me at Rudeseal@hemi.com. Variables in Quake-C are data objects that may change in value. A variable is given a name by means of a definition, which allocates storage space for the data and associates the storage location with the variable name. The Quake-C language defines four fundamental representations of data: integer character floating-point double floating-point Each of these is associated with a special type specifier: int specifies an integer variable char specifies a character variable float specifies a fractional-number variable double specifies a fractional-number variable with more decimal places Any of the type specifiers may be qualified with the type qualifier CONST, which specifies that the variable must not be changed after it is initialised. A data definition is of the following general form: <type-specifier> <name>; A variable name is also called an identifier. The following are some examples of simple data definitions in Quake-C: int goals; // integer variable char c; // character value eg: 'b' float balance; // bank balance const double x = 5; // high-precision variable // value fixed when set